local fns = {} -- a table to store local functions in so that we don't hit the 60 upvalues limit

local assets =
{
    Asset("ANIM", "anim/bboy_saddle.zip"),

    Asset("ANIM", "anim/beefalo_basic.zip"),
    Asset("ANIM", "anim/beefalo_actions.zip"),
    Asset("ANIM", "anim/beefalo_actions_domestic.zip"),
    Asset("ANIM", "anim/beefalo_actions_quirky.zip"),
    Asset("ANIM", "anim/beefalo_build.zip"),

    Asset("ANIM", "anim/beefalo_domesticated.zip"),
    Asset("ANIM", "anim/beefalo_personality_docile.zip"),
    Asset("ANIM", "anim/beefalo_personality_ornery.zip"),
    Asset("ANIM", "anim/beefalo_personality_pudgy.zip"),

    Asset("ANIM", "anim/beefalo_skin_change.zip"),

    Asset("ANIM", "anim/beefalo_carrat_idles.zip"),
    Asset("ANIM", "anim/yotc_carrat_colour_swaps.zip"),

    Asset("ANIM", "anim/beefalo_fx.zip"),
    Asset("ANIM", "anim/poop_cloud.zip"),
    Asset("ANIM", "anim/beefalo_revive.zip"),

    Asset("SOUND", "sound/beefalo.fsb"),

    Asset("MINIMAP_IMAGE", "beefalo_domesticated"),
}

local prefabs =
{
    "meat",
    "poop",
    "beefalowool",
    "horn",
    "carrat",
    "explode_reskin",
    "spawn_fx_medium",
}

local brain = require("brains/beefalobrain")

SetSharedLootTable('bboy_paper_beefalo',
    {
        { 'papyrus',       1.00 },
        { 'papyrus',       1.00 },
        { 'papyrus',       1.00 },
        { 'nightmarefuel', 0.33 },
    })

local sounds =
{
    walk = "dontstarve/beefalo/walk",
    grunt = "dontstarve/beefalo/grunt",
    yell = "dontstarve/beefalo/yell",
    swish = "dontstarve/beefalo/tail_swish",
    curious = "dontstarve/beefalo/curious",
    angry = "dontstarve/beefalo/angry",
    sleep = "dontstarve/beefalo/sleep",
}

local function OnAttacked(inst, data)
    if inst._ridersleeptask ~= nil then
        inst._ridersleeptask:Cancel()
        inst._ridersleeptask = nil
    end
    inst._ridersleep = nil
    if inst.components.rideable:IsBeingRidden() then
        if not inst.components.domesticatable:IsDomesticated() then
            inst.components.domesticatable:DeltaDomestication(TUNING.BEEFALO_DOMESTICATION_ATTACKED_DOMESTICATION)
            inst.components.domesticatable:DeltaObedience(TUNING.BEEFALO_DOMESTICATION_ATTACKED_OBEDIENCE)
        end
    end
end

fns.RemoveName = function(inst)
end

local function ShouldAcceptItem(inst, item, giver, count)
    if inst.components.trader.abletoaccepttest ~= nil then
        return false -- 如果有自定义拒绝逻辑,就走自定义的
    end

    -- 没有攻击目标
    return not inst.components.combat:HasTarget()
end

local function OnGetItemFromPlayer(inst, giver, item)
end

local function OnRefuseItem(inst, giver, item)
    inst.sg:GoToState("refuse")
    if inst.components.sleeper:IsAsleep() then
        inst.components.sleeper:WakeUp()
    end
end

local function CalculateBuckDelay(inst)
    -- 驯化度
    local domestication =
        inst.components.domesticatable ~= nil
        and inst.components.domesticatable:GetDomestication()
        or 0

    -- 是否有牛鞍吧,代码意思是有归属牛群,我直接删掉固定1倍率
    local moodmult = 1

    -- 是否被刮毛,我也删了固定1倍率
    local beardmult = 1

    -- 是否被驯化
    local domesticmult =
        inst.components.domesticatable:IsDomesticated()
        and 1
        or TUNING.BEEFALO_BUCK_TIME_UNDOMESTICATED_MULT

    -- 骑行者技能树,维持默认吧
    local rider = inst.components.rideable ~= nil and inst.components.rideable:GetRider()

    local skillmult =
        rider ~= nil and rider.components.skilltreeupdater ~= nil and rider.components.skilltreeupdater:HasSkillTag("beefalobucktime")
        and TUNING.SKILLS.WATHGRITHR.WATHGRITHR_BEEFALO_BUCK_TIME_MOD
        or 1

    local basedelay = Remap(domestication, 0, 1, TUNING.BEEFALO_MIN_BUCK_TIME, TUNING.BEEFALO_MAX_BUCK_TIME)

    return basedelay * moodmult * beardmult * domesticmult * skillmult
end

-- 甩下玩家
local function OnBuckTime(inst)
    -- V2C: 重新安排，因为：Buck() 无法保证！
    inst._bucktask = inst:DoTaskInTime(1 + math.random(), OnBuckTime)
    inst.components.rideable:Buck(true) -- 温和的放下
end

local function OnObedienceDelta(inst, data)
    inst.components.rideable:SetSaddleable(data.new >= TUNING.BEEFALO_SADDLEABLE_OBEDIENCE)

    if data.new > data.old and inst._bucktask ~= nil then
        -- 重置甩玩家计时器,这个是新版本玩家坐在牛上喂食
        inst._bucktask:Cancel()
        inst._bucktask = inst:DoTaskInTime(CalculateBuckDelay(inst), OnBuckTime)
    end
end

local function OnDeath(inst, data)
    inst:AddTag("NOCLICK")

    inst.persists = false

    -- 正在被骑
    if inst.components.rideable:IsBeingRidden() then
        -- 当玩家骑乘时,SG不会执行死亡事件
        -- 函数 OnRiderChanged 中会处理死亡事件
        inst.components.rideable:Buck(true)
    end
end

local function DomesticationTriggerFn(inst)
    return
        inst.components.rideable:IsBeingRidden() == true
end

local function OnStarving(inst, dt)
    inst.components.domesticatable:DeltaObedience(0)
end

local function OnHealthDelta(inst, data)
    if data.oldpercent >= 0.2 and
        data.newpercent < 0.2 and
        inst.components.rideable.rider ~= nil then
        inst.components.rideable.rider:PushEvent("mountwounded")
    end
end

local function DoRiderSleep(inst, sleepiness, sleeptime)
    inst._ridersleeptask = nil
    inst.components.sleeper:AddSleepiness(sleepiness, sleeptime)
end

local function OnRiderChanged(inst, data)
    -- 重置甩玩家计时器
    if inst._bucktask ~= nil then
        inst._bucktask:Cancel()
        inst._bucktask = nil
    end

    -- 骑行者睡眠计时器关闭
    if inst._ridersleeptask ~= nil then
        inst._ridersleeptask:Cancel()
        inst._ridersleeptask = nil
    end

    if data.newrider ~= nil then
        if inst.components.sleeper ~= nil then
            inst.components.sleeper:WakeUp()
        end
        inst._bucktask = inst:DoTaskInTime(CalculateBuckDelay(inst), OnBuckTime)
        -- 当前坐标记为闲逛锚点
        inst.components.knownlocations:RememberLocation("loiteranchor", inst:GetPosition())
        if inst.sg ~= nil then
            inst.sg:GoToState("idle")
        end
    elseif inst.components.health:IsDead() then
        if inst.sg.currentstate.name ~= "death" then
            inst.sg:GoToState("death")
        end
    elseif inst.components.sleeper ~= nil then
        inst.components.sleeper:StartTesting()
        if inst._ridersleep ~= nil then
            local sleeptime = inst._ridersleep.sleeptime + inst._ridersleep.time - GetTime()
            if sleeptime > 2 then
                inst._ridersleeptask = inst:DoTaskInTime(0, DoRiderSleep, inst._ridersleep.sleepiness, sleeptime)
            end
            inst._ridersleep = nil
        end
    end
end

local function PotentialRiderTest(inst, potential_rider)
    local leader = inst.components.follower:GetLeader()
    if leader == nil or leader.components.inventoryitem == nil then
        return true
    end

    local leader_owner = leader.components.inventoryitem:GetGrandOwner()
    return (leader_owner == nil or leader_owner == potential_rider)
end

local function _OnRefuseRider(inst)
    if inst.components.sleeper:IsAsleep() and not inst.components.health:IsDead() then
        -- this needs to happen after the stategraph
        inst.components.sleeper:WakeUp()
    end
end

local function OnRefuseRider(inst, data)
    inst:DoTaskInTime(0, _OnRefuseRider)
end

local function OnRiderSleep(inst, data)
    inst._ridersleep = inst.components.rideable:IsBeingRidden() and {
        time = GetTime(),
        sleepiness = data.sleepiness,
        sleeptime = data.sleeptime,
    } or nil
end

local WAKE_TO_FOLLOW_DISTANCE = 15
local function ShouldWakeUp(inst)
    return DefaultWakeTest(inst)
        or (inst.components.follower.leader ~= nil
            and not inst.components.follower:IsNearLeader(WAKE_TO_FOLLOW_DISTANCE))
end

local TWEEN_TARGET = { 0, 0, 0, 1 }
local TWEEN_TIME = 13 * FRAMES
fns.OnDespawnRequest = function(inst)
    local fx = SpawnPrefab("spawn_fx_medium")
    fx.Transform:SetPosition(inst.Transform:GetWorldPosition())
    inst._marked_for_despawn = true
    inst.components.colourtweener:StartTween(TWEEN_TARGET, TWEEN_TIME, inst.Remove)
end

local SLEEP_NEAR_LEADER_DISTANCE = 10
local function MountSleepTest(inst)
    return not inst.components.rideable:IsBeingRidden()
        and DefaultSleepTest(inst)
        and (inst.components.follower.leader == nil
            or inst.components.follower:IsNearLeader(SLEEP_NEAR_LEADER_DISTANCE))
end

-- 官方行为中存在这些函数,所以留空保留
local function UpdateDomestication(inst)
end
local function testforcarratexit(inst)
end

-- 官方大脑中有这写函数,所以保留返回默认值
fns.GetBeefBellOwner = function(inst)
    return nil
end
function fns.GetIsInMood(inst)
    return false
end

local function ShouldBeg(inst)
    return false
end

local function ApplyBuildOverrides(inst, animstate)
    if animstate ~= nil and animstate ~= inst.AnimState then
        animstate:AddOverrideBuild("beefalo_build")
    else
        animstate:SetBuild("beefalo_build")
    end

    if fns.GetIsInMood(inst) then
        animstate:Show("HEAT")
    else
        animstate:Hide("HEAT")
    end
end

local function ClearBuildOverrides(inst, animstate)
    if animstate ~= inst.AnimState then
        animstate:ClearOverrideBuild("beefalo_build")
    end
    -- 所有面部表情都有相同的符号
    animstate:ClearOverrideBuild("beefalo_personality_docile")
end

local function OnTimerDone(inst, data)
    if data.name == "disappear" then
        inst.sg:GoToState("skin_change", function()
            inst:Remove()
        end)
    end
end

fns.OnSave = function(inst, data)
end

fns.OnLoad = function(inst, data)
end

fns.OnLoadPostPass = function(inst, data)
end

local function beefalo()
    local inst = CreateEntity()

    inst.entity:AddTransform()
    inst.entity:AddAnimState()
    inst.entity:AddSoundEmitter()
    inst.entity:AddDynamicShadow()
    inst.entity:AddMiniMapEntity()
    inst.entity:AddNetwork()

    MakeCharacterPhysics(inst, 100, .5)

    inst.DynamicShadow:SetSize(6, 2)
    inst.Transform:SetSixFaced()

    inst.AnimState:SetBank("beefalo")
    inst.AnimState:SetBuild("beefalo_build")
    inst.AnimState:AddOverrideBuild("poop_cloud")
    inst.AnimState:AddOverrideBuild("beefalo_carrat_idles")
    inst.AnimState:PlayAnimation("idle_loop", true)
    inst.AnimState:Hide("HEAT")
    inst.AnimState:OverrideSymbol("swap_saddle", "bboy_saddle", "swap_saddle")

    inst:AddTag("beefalo")        -- 牛
    inst:AddTag("animal")         -- 动物
    inst:AddTag("largecreature")  -- 大生物

    inst:AddTag("bearded")        -- 有牛毛
    inst:AddTag("trader")         -- 可交易对象
    inst:AddTag("domesticatable") -- 可以驯化

    inst:AddTag("companion")
    inst:AddTag("notraptrigger")

    -- 用于获取牛毛的标签。由客户使用。
    inst:AddTag("has_beard")

    inst.sounds = sounds -- 牛牛的声音组,维持默认

    inst.entity:SetPristine()

    if not TheWorld.ismastersim then
        return inst
    end

    inst:AddComponent("planardamage")
    inst:AddComponent("bloomer") -- 和官方保持一致吧,貌似是泛光相关的组件

    inst:AddComponent("combat")
    inst.components.combat.hiteffectsymbol = "beefalo_body"
    inst.components.combat:SetDefaultDamage(TUNING.BEEFALO_DAMAGE.DEFAULT)

    inst:AddComponent("health")
    inst.components.health:SetMaxHealth(TUNING.BEEFALO_HEALTH)
    inst.components.health.nofadeout = true
    -- 缓慢回血,维持默认
    inst.components.health:StartRegen(TUNING.BEEFALO_HEALTH_REGEN, TUNING.BEEFALO_HEALTH_REGEN_PERIOD)
    inst:ListenForEvent("death", OnDeath)             -- 触发死亡事件后的一些判断处理
    inst:ListenForEvent("healthdelta", OnHealthDelta) -- 掉血信息同步给骑乘者

    -- 掉落组件
    inst:AddComponent("lootdropper")
    -- inst.components.lootdropper:SetChanceLootTable('bboy_paper_beefalo')

    inst:AddComponent("inspectable")

    -- 地点记录组件
    inst:AddComponent("knownlocations")

    inst:AddComponent("leader")
    inst:AddComponent("follower")
    inst.components.follower.maxfollowtime = 480
    inst.components.follower.canaccepttarget = false

    inst:ListenForEvent("attacked", OnAttacked)

    inst:AddComponent("rideable")
    inst.components.rideable:SetRequiredObedience(TUNING.BEEFALO_MIN_BUCK_OBEDIENCE) -- 可骑行基础顺从度
    inst.components.rideable:SetCustomRiderTest(PotentialRiderTest)                  -- 骑行条件
    inst:ListenForEvent("refusedrider", OnRefuseRider)
    inst.components.rideable.canride = false                                         -- 无法骑行

    inst:AddComponent("trader")
    inst.components.trader:SetAcceptTest(ShouldAcceptItem)
    inst.components.trader.onaccept = OnGetItemFromPlayer
    inst.components.trader.onrefuse = OnRefuseItem
    inst.components.trader.deleteitemonaccept = false

    -- domesticated 组件需要,必须保留 hunger 组件
    inst:AddComponent("hunger")
    inst.components.hunger:SetMax(TUNING.BEEFALO_HUNGER)
    inst.components.hunger:SetRate(TUNING.BEEFALO_HUNGER_RATE)
    inst.components.hunger:SetPercent(0)
    inst.components.hunger:SetOverrideStarveFn(OnStarving)

    inst:AddComponent("domesticatable")
    inst.components.domesticatable:SetDomesticationTrigger(DomesticationTriggerFn) -- 骑行中触发驯化值增加
    inst.components.domesticatable.obedience = 1.0
    inst.components.domesticatable.domestication = 1.0
    inst.components.domesticatable.domesticated = true -- 直接驯化好

    MakeLargeBurnableCharacter(inst, "beefalo_body")   -- 可以被点燃
    MakeLargeFreezableCharacter(inst, "beefalo_body")  -- 可以被冰冻

    -- 移动组件,维持官方默认
    inst:AddComponent("locomotor")
    inst.components.locomotor.walkspeed = TUNING.BEEFALO_WALK_SPEED
    inst.components.locomotor.runspeed = TUNING.BEEFALO_RUN_SPEED.DEFAULT

    -- 睡眠组件,维持官方默认
    inst:AddComponent("sleeper")
    inst.components.sleeper:SetResistance(3)
    inst.components.sleeper.sleeptestfn = MountSleepTest
    inst.components.sleeper.waketestfn = ShouldWakeUp

    inst.ApplyBuildOverrides = ApplyBuildOverrides
    inst.ClearBuildOverrides = ClearBuildOverrides

    inst:AddComponent("timer")
    inst.components.timer:StartTimer("disappear", 1440)
    inst:ListenForEvent("timerdone", OnTimerDone)

    inst._bucktask = nil
    inst.domesticationPending = true -- 就是驯化好的

    inst.ShouldBeg = ShouldBeg
    inst.GetBeefBellOwner = fns.GetBeefBellOwner
    inst.GetIsInMood = fns.GetIsInMood
    inst.testforcarratexit = testforcarratexit
    inst.UpdateDomestication = UpdateDomestication
    inst:ListenForEvent("obediencedelta", OnObedienceDelta) -- 顺从度变化
    inst:ListenForEvent("riderchanged", OnRiderChanged)     -- 变更骑行者,或者骑行者上牛
    inst:ListenForEvent("ridersleep", OnRiderSleep)
    inst:ListenForEvent("despawn", fns.OnDespawnRequest)

    inst:AddComponent("uniqueid")
    inst:AddComponent("beefalometrics")
    inst:AddComponent("drownable")
    inst:AddComponent("colouradder")

    inst:AddComponent("colourtweener")

    inst:AddComponent("markable_proxy")

    --inst._marked_for_despawn = nil

    inst:SetBrain(brain)
    inst:SetStateGraph("SGBeefalo")

    inst.OnSave = fns.OnSave
    inst.OnLoad = fns.OnLoad
    inst.OnLoadPostPass = fns.OnLoadPostPass

    return inst
end

return Prefab("bboy_carrying_beefalo", beefalo, assets, prefabs)
